/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.activiti.rest.service.api.runtime.task;

import org.activiti.engine.ActivitiException;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.task.Task;
import org.activiti.rest.exception.ActivitiForbiddenException;
import org.activiti.rest.service.api.engine.variable.RestVariable;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Frederik Heremans
 */
@RestController
public class TaskResource extends TaskBaseResource {

    @RequestMapping(value = "/runtime/tasks/{taskId}", method = RequestMethod.GET, produces = "application/json")
    public TaskResponse getTask(@PathVariable String taskId, HttpServletRequest request) {
        return restResponseFactory.createTaskResponse(getTaskFromRequest(taskId));
    }

    @RequestMapping(value = "/runtime/tasks/{taskId}", method = RequestMethod.PUT, produces = "application/json")
    public TaskResponse updateTask(@PathVariable String taskId,
                                   @RequestBody TaskRequest taskRequest, HttpServletRequest request) {

        if (taskRequest == null) {
            throw new ActivitiException("A request body was expected when updating the task.");
        }

        Task task = getTaskFromRequest(taskId);

        // Populate the task properties based on the request
        populateTaskFromRequest(task, taskRequest);

        // Save the task and fetch agian, it's possible that an assignment-listener has updated
        // fields after it was saved so we can't use the in-memory task
        taskService.saveTask(task);
        task = taskService.createTaskQuery().taskId(task.getId()).singleResult();

        return restResponseFactory.createTaskResponse(task);
    }

    @RequestMapping(value = "/runtime/tasks/{taskId}", method = RequestMethod.POST)
    @ResponseStatus(value = HttpStatus.OK)
    public void executeTaskAction(@PathVariable String taskId, @RequestBody TaskActionRequest actionRequest) {
        if (actionRequest == null) {
            throw new ActivitiException("A request body was expected when executing a task action.");
        }

        Task task = getTaskFromRequest(taskId);

        if (TaskActionRequest.ACTION_COMPLETE.equals(actionRequest.getAction())) {
            completeTask(task, actionRequest);

        } else if (TaskActionRequest.ACTION_CLAIM.equals(actionRequest.getAction())) {
            claimTask(task, actionRequest);

        } else if (TaskActionRequest.ACTION_DELEGATE.equals(actionRequest.getAction())) {
            delegateTask(task, actionRequest);

        } else if (TaskActionRequest.ACTION_RESOLVE.equals(actionRequest.getAction())) {
            resolveTask(task, actionRequest);

        } else {
            throw new ActivitiIllegalArgumentException("Invalid action: '" + actionRequest.getAction() + "'.");
        }
    }

    @RequestMapping(value = "/runtime/tasks/{taskId}", method = RequestMethod.DELETE)
    public void deleteTask(@PathVariable String taskId, @RequestParam(value = "cascadeHistory", required = false) Boolean cascadeHistory,
                           @RequestParam(value = "deleteReason", required = false) String deleteReason, HttpServletResponse response) {

        Task taskToDelete = getTaskFromRequest(taskId);
        if (taskToDelete.getExecutionId() != null) {
            // Can't delete a task that is part of a process instance
            throw new ActivitiForbiddenException("Cannot delete a task that is part of a process-instance.");
        }

        if (cascadeHistory != null) {
            // Ignore delete-reason since the task-history (where the reason is recorded) will be deleted anyway
            taskService.deleteTask(taskToDelete.getId(), cascadeHistory);
        } else {
            // Delete with delete-reason
            taskService.deleteTask(taskToDelete.getId(), deleteReason);
        }
        response.setStatus(HttpStatus.NO_CONTENT.value());
    }

    protected void completeTask(Task task, TaskActionRequest actionRequest) {
        if (actionRequest.getVariables() != null) {
            Map<String, Object> variablesToSet = new HashMap<String, Object>();
            for (RestVariable var : actionRequest.getVariables()) {
                if (var.getName() == null) {
                    throw new ActivitiIllegalArgumentException("Variable name is required");
                }

                Object actualVariableValue = restResponseFactory.getVariableValue(var);
                variablesToSet.put(var.getName(), actualVariableValue);
            }

            taskService.complete(task.getId(), variablesToSet);
        } else {
            taskService.complete(task.getId());
        }

    }

    protected void resolveTask(Task task, TaskActionRequest actionRequest) {
        taskService.resolveTask(task.getId());
    }

    protected void delegateTask(Task task, TaskActionRequest actionRequest) {
        if (actionRequest.getAssignee() == null) {
            throw new ActivitiIllegalArgumentException("An assignee is required when delegating a task.");
        }
        taskService.delegateTask(task.getId(), actionRequest.getAssignee());
    }

    protected void claimTask(Task task, TaskActionRequest actionRequest) {
        // In case the task is already claimed, a ActivitiTaskAlreadyClaimedException is thrown and converted to
        // a CONFLICT response by the ExceptionHandlerAdvice
        taskService.claim(task.getId(), actionRequest.getAssignee());
    }
}
